package nl.harmidi.midi;

import java.util.ArrayList;
import java.util.List;

import nl.harmidi.harmonictable.Spot;
import nl.harmidi.harmonictable.TableModel;

public class MyMidiEventsToTableListener extends MyMidiEventsLatencyListener {

	private MidiChannelFilter filter;

	private int middleColumn = 9; // start out near the middle column

	private long noteOffDelay = 0;

	private final TableModel tableModel;

	public MyMidiEventsToTableListener(final TableModel tableModel) {
		this.tableModel = tableModel;
	}

	/*
	 * Beware, this method can be called by multiple threads. Be aware that all
	 * class attributes could be changed during this method by another thread.
	 */
	@Override
	public void handleEvent(MyMidiEvent event) {
		if (filter.allow(event.getChannel())) {

			List<Spot> hotSpots = findHotSpots(event);

			if (hotSpots.size() > 0) {
				if (event.getType() == MyMidiEvent.TYPE.NOTE_ON) {
					handleNoteOn(hotSpots, event);
				} else if (event.getType() == MyMidiEvent.TYPE.NOTE_OFF) {
					handleNoteOff(hotSpots, event);
				}
			}
		}
	}

	public void handleNoteOn(List<Spot> hotSpots, MyMidiEvent event) {

		// middle-notes oriented viewer

		if (hotSpots.size() == 3) {
			int rowWidth0 = hotSpots.get(0).getRowNumber() % 2 == 0 ? 8 : 9; // even
																				// is
																				// length
																				// 8
			int rowWidth1 = hotSpots.get(1).getRowNumber() % 2 == 0 ? 8 : 9;
			int rowWidth2 = hotSpots.get(2).getRowNumber() % 2 == 0 ? 8 : 9;
			int spotCol0 = (hotSpots.get(0).getColumnNumber() * 2) - (rowWidth0 == 9 ? 1 : 0);
			int spotCol1 = (hotSpots.get(1).getColumnNumber() * 2) - (rowWidth1 == 9 ? 1 : 0);
			int spotCol2 = (hotSpots.get(2).getColumnNumber() * 2) - (rowWidth2 == 9 ? 1 : 0);

			int distance0 = Math.abs(spotCol0 - middleColumn);
			int distance1 = Math.abs(spotCol1 - middleColumn);
			int distance2 = Math.abs(spotCol2 - middleColumn);

			int theMinst = Math.min(distance2, Math.min(distance0, distance1));

			if (distance1 == theMinst) {
				hotSpots.get(1).send(event);
			} else if (distance0 == theMinst) {
				hotSpots.get(0).send(event);
			} else if (distance2 == theMinst) {
				hotSpots.get(2).send(event);
			}
		} else if (hotSpots.size() == 2) {

			int rowWidth0 = hotSpots.get(0).getRowNumber() % 2 == 0 ? 8 : 9; // even
																				// is
																				// length
																				// 8
			int rowWidth1 = hotSpots.get(1).getRowNumber() % 2 == 0 ? 8 : 9;
			int spotCol0 = (hotSpots.get(0).getColumnNumber() * 2) - (rowWidth0 == 9 ? 1 : 0);
			int spotCol1 = (hotSpots.get(1).getColumnNumber() * 2) - (rowWidth1 == 9 ? 1 : 0);

			if (Math.abs(spotCol0 - middleColumn) <= Math.abs(spotCol1 - middleColumn)) {
				hotSpots.get(0).send(event);
			} else {
				hotSpots.get(1).send(event);
			}

		} else if (hotSpots.size() == 1) {
			hotSpots.get(0).send(event);
		}

	}

	private void handleNoteOff(List<Spot> hotSpots, MyMidiEvent event) {
		sleep(noteOffDelay); // luckily, all events are handled in their own
								// thread, so sleeping will only affect this
								// event

		// why bother with which of the hotspots is the one that's lit; off-ing
		// them all is much quicker
		for (Spot spot : hotSpots) {
			spot.startVelocityFade(1);
		}

	}

	public MidiChannelFilter getFilter() {
		return filter;
	}

	public void setFilter(MidiChannelFilter filter) {
		this.filter = filter;
	}

	private List<Spot> findHotSpots(MyMidiEvent event) {
		List<Spot> hotSpots = new ArrayList<Spot>();
		for (Spot spot : tableModel.getAllSpots()) {
			if (event.getNoteNumber() == spot.getNoteNumber()) {
				hotSpots.add(spot);
				if (hotSpots.size() >= 3) {
					break;
				}
			}
		}
		return hotSpots;
	}

	public void setNoteOffDelay(long noteOffDelay) {
		this.noteOffDelay = noteOffDelay;
	}

}
